/*!
 * \file cosecha.cpp
 *
 * \date Sep 29, 2014
 * \author correa
 */

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <cstring>
#include <string>
#include <map>
#include <vector>
#include <limits>

#include <dirent.h>
#include <sys/types.h>

using namespace std;

#define _FILE_	0x8
#define _DIR_	0x4

#define _TIME_STR_			"Total (root+branch&cut) ="
#define _LB_STR_			"Solution value  ="
#define _NINITMODEL_STR_	"Clique in initial model  ="
#define _NVERT_STR_			"Número de vertices ="
#define _DENS_STR_			"Densidad  ="

#define _NCLIQUE_STR_		"Clique separated  ="
#define _NRANK_STR_			"Rango separated  ="
#define _NWRANK_STR_		"Rango con pesos separated  ="
#define _NPL_STR_			"Número de llamadas a PL  ="

typedef struct
{
	string nombre;
	int nvert;
	double dens;
	int alpha;
} Instance;

bool fncomp (Instance lhs, Instance rhs) {return lhs.nombre != rhs.nombre ? lhs.nombre < rhs.nombre : lhs.nvert != rhs.nvert ? lhs.nvert < rhs.nvert : lhs.dens < rhs.dens;}

map<string, Instance> conocidas { 	{ "brock200_2", { "", 200, 0.50, 12 } },
									{ "brock200_4", { "", 200, 0.34, 17 } },
									{ "brock400_2", { "", 400, 0.25, 12 } },
									{ "brock400_4", { "", 400, 0.25, 17 } },
									{ "c-fat500-1", { "", 500, 0.96, 14 } },
									{ "c-fat500-2", { "", 500, 0.93, 26 } },
									{ "C125.9", { "", 125, 0.10, 34 } },
									{ "C250.9", { "", 250, 0.10, 44 } },
									{ "keller4", { "", 171, 0.35, 11 } },
									{ "p_hat300-2", { "", 300, 0.51, 25 } },
									{ "p_hat300-3", { "", 300, 0.26, 36 } },
									{ "san200_0.7_2", {	"", 200, 0.30, 18 } },
									{ "san200_0.9_3", { "", 200, 0.10, 44 } },
									{ "san400_0.5_1", {	"", 400, 0.50, 13 } } };

map<string, double> pardalos { 	{ "brock200_2", 20.99 },
								{ "brock200_4", 29.93 },
								{ "brock400_2", 63.84 },
								{ "brock400_4", 63.89 },
								{ "c-fat500-1", 14 },
								{ "c-fat500-2", 26.97 },
								{ "C125.9", 41.26 },
								{ "C250.9", 69.76 },
								{ "keller4", 14.83 },
								{ "p_hat300-2", 33.81 },
								{ "p_hat300-3", 54.12 },
								{ "san200_0.7_2", 18.50 },
								{ "san200_0.9_3", 44 },
								{ "san400_0.5_1", 13.24 },
								{ "100_10", 34.93 },
								{ "100_20", 25.73 },
								{ "150_10", 48.05 },
								{ "150_20", 34.46 } };

class Corrida
{
public:
	int npruebas;
	double LB;
	double UB;
	double time;
	double ninitmodel;
	double nclique;
	double nrank;
	double nwrank;
	double npl;

	Corrida() : npruebas(0), LB(0), UB(numeric_limits<double>::max()), time(0), ninitmodel(0), nclique(0), nrank(0), nwrank(0), npl(0) {};

	Corrida(ifstream& file);
	void accumulate(ifstream& file);
	void promedio();

	void print(Instance graph, int K);
};

Corrida::Corrida(ifstream& file) : Corrida()
{
	accumulate(file);
}

void Corrida::accumulate(ifstream& file)
{
	string line;
	if (file.is_open())
	{
		if (npruebas == 0)
			UB = 0.0;
		npruebas++;
		double lastUB = 0.0;
		double lastLB;

		while ( getline (file,line) )
		{
			if (line.empty())
				continue;

			int initpos = line.find_first_not_of(" ");

			if (line[initpos] == '0')
			{
				int pos1 = line.find_first_not_of(" ",initpos+1);
				pos1 = line.find_first_not_of(" ",pos1+1);
				int pos2 = line.find_first_of(" ",pos1);
				lastUB = atof(line.substr(pos1, pos2).data());
			}
			else if ((initpos = line.find(_TIME_STR_)) != string::npos)
			{
				time += atof(line.substr(initpos+strlen(_TIME_STR_)).data());
			}
			else if ((initpos = line.find(_LB_STR_)) != string::npos)
			{
				lastLB = atoi(line.substr(initpos+strlen(_LB_STR_)).data());
				LB += lastLB;
			}
			else if ((initpos = line.find(_NINITMODEL_STR_)) != string::npos)
			{
				ninitmodel += atoi(line.substr(initpos+strlen(_NINITMODEL_STR_)).data());
			}
			else if ((initpos = line.find(_NCLIQUE_STR_)) != string::npos)
			{
				nclique += atoi(line.substr(initpos+strlen(_NCLIQUE_STR_)).data());
			}
			else if ((initpos = line.find(_NRANK_STR_)) != string::npos)
			{
				nrank += atoi(line.substr(initpos+strlen(_NRANK_STR_)).data());
			}
			else if ((initpos = line.find(_NWRANK_STR_)) != string::npos)
			{
				nwrank += atoi(line.substr(initpos+strlen(_NWRANK_STR_)).data());
			}
			else if ((initpos = line.find(_NPL_STR_)) != string::npos)
			{
				npl += atoi(line.substr(initpos+strlen(_NPL_STR_)).data());
			}
		}

		if (lastUB < lastLB)
			lastUB = lastLB;
		UB += lastUB;
	}
}

void Corrida::promedio()
{
//	cout << " npruebas=" << npruebas << endl;

	if (npruebas <= 1)
		return;

	LB /= npruebas;
	UB /= npruebas;
	time /= npruebas;
	ninitmodel /= npruebas;
	nclique /= npruebas;
	nrank /= npruebas;
	nwrank /= npruebas;
	npl /= npruebas;
}

void Corrida::print(Instance graph, int K)
{
	cout << "Nombre: " << graph.nombre << endl;
	cout << "Número de vertices: " << graph.nvert << endl;
	cout << "Densidad: " << graph.dens << endl;
	cout << "Alpha: " << graph.alpha << endl;
	cout << "D = " << K << endl;
	cout << "LB = " << LB << endl;
	cout << "UB = " << UB << endl;
	cout << "Número cliques modelo: " << ninitmodel << endl;
	cout << "Cliques separadas: " << nclique << endl;
	cout << "Rango separadas: " << nrank << endl;
	cout << "Rango con pesos separadas: " << nwrank << endl;
	cout << "Llamadas a pl: " << npl << endl;
	cout << "Time = " << time << endl;
}

void leerCorridasDimacs(string dirname, map<Instance, vector<Corrida>, bool(*)(Instance, Instance)>& corridas)
{
	string dimacsDirName = dirname+"/dimacs";
	DIR * dimacsDir = opendir(dimacsDirName.data());
	struct dirent * dimacsEntry = readdir(dimacsDir);
	while (dimacsEntry != NULL)
	{
		if (dimacsEntry->d_type == _DIR_ && strcmp(dimacsEntry->d_name, ".") != 0 && strcmp(dimacsEntry->d_name, "..") != 0)
		{
			string graphDirName = dimacsDirName+"/"+dimacsEntry->d_name;
			DIR * graphDir = opendir(graphDirName.data());
			struct dirent * graphEntry = readdir(graphDir);
			while (graphEntry != NULL)
			{
				if (graphEntry->d_type == _FILE_)
				{
					string graphName = string(graphEntry->d_name);
					int K = atoi(graphName.substr(graphName.rfind(".K")+2).data());
					ifstream graphStream((graphDirName+"/"+graphName).data());
					graphName = graphName.substr(0, graphName.rfind(".K"));

					Instance instance = {"",0,0,0};
					map<string, Instance>::iterator it = conocidas.find(graphName);
					if (it != conocidas.end())
						instance = it->second;
					instance.nombre = graphName;

					map<Instance, vector<Corrida>, bool(*)(Instance, Instance)>::iterator corrida = corridas.find(instance);
					if (corrida == corridas.end())
					{
						corridas[instance] = vector<Corrida>(K);
						corridas[instance].push_back(Corrida(graphStream));
					}
					else if (K >= corrida->second.size())
					{
						corrida->second.resize(K);
						corrida->second.push_back(Corrida(graphStream));
					}
					else
						corrida->second[K] = Corrida(graphStream);
					graphStream.close();
				}
				graphEntry = readdir(graphDir);
			}
		}
		dimacsEntry = readdir(dimacsDir);
	}
}

void leerCorridasRand(string dirname, map<Instance, vector<Corrida>, bool(*)(Instance, Instance)>& corridas)
{
	string randDirName = dirname+"/rand";
	DIR * randDir = opendir(randDirName.data());
	struct dirent * randEntry = readdir(randDir);
	while (randEntry != NULL)
	{
		if (randEntry->d_type == _DIR_ && strcmp(randEntry->d_name, ".") != 0 && strcmp(randEntry->d_name, "..") != 0)
		{
			string graphDirName = randDirName+"/"+randEntry->d_name;
			DIR * graphDir = opendir(graphDirName.data());
			struct dirent * graphEntry = readdir(graphDir);
			while (graphEntry != NULL)
			{
				if (graphEntry->d_type == _FILE_)
				{
					string graphName = string(graphEntry->d_name);
					int K = atoi(graphName.substr(graphName.rfind(".K")+2).data());
					ifstream graphStream((graphDirName+"/"+graphName).data());
					graphName = graphName.substr(0, graphName.rfind(".K"));

					string config(randEntry->d_name);
					int prob = 100-atoi(config.substr(config.find_first_of("_")+1).data());
					config.replace(config.find_first_of("_")+1, string::npos, to_string(prob));
					Instance instance = { config, atoi(config.substr(0, config.find_first_of("_")).data()), atof(config.substr(config.find_first_of("_")+1).data())/100, 0 };

					map<Instance, vector<Corrida>, bool(*)(Instance, Instance)>::iterator corrida = corridas.find(instance);
					if (corrida == corridas.end())
					{
						corridas[instance] = vector<Corrida>(K);
						corridas[instance].push_back(Corrida(graphStream));
					}
					else if (K >= corrida->second.size())
					{
						corrida->second.resize(K);
						corrida->second.push_back(Corrida(graphStream));
					}
					else
						corrida->second[K].accumulate(graphStream);
					graphStream.close();
				}
				graphEntry = readdir(graphDir);
			}
		}
		randEntry = readdir(randDir);
	}
}

string toString(double valor, int decimales, bool bold=false)
{
	if (valor == 0)
		return "--";
	string valorstr = to_string(valor);
	int pointpos = valorstr.find_first_of('.');
	if (decimales > 0 && valorstr.substr(pointpos+1,pointpos+decimales+1).find_first_not_of('0') == string::npos)
		decimales = 0;
	if (bold)
		return "{\\bf "+valorstr.substr(0, pointpos+decimales+ (decimales == 0 ? 0 : 1))+"}";
	return valorstr.substr(0, pointpos+decimales+ (decimales == 0 ? 0 : 1));
}

void escribirTabla(map<Instance, vector<Corrida>, bool(*)(Instance, Instance)> corridas, string outname)
{
	ofstream outStream(outname.data());

	char encabezado[] = "\\begin{tabular}{ccccccccccccccc} \\hline\n\
\%Encabezado fila 1\n\
\\multicolumn{3}{l}{Instances} & $D$ & \\multicolumn{4}{l}{Root subproblem} & \\quad & \\multicolumn{2}{l}{Time (sec.)} & \\quad & \\multicolumn{2}{l}{Number of cuts} \\\\ \\cline{1-3} \\cline{5-8} \\cline{10-11} \\cline{13-14}\n\
\%Encabezado fila 2\n\
$G=(V,E)$ & $|V|$/Dens. & $\\alpha(G)$ && LB & \\multicolumn{3}{l}{UB per type of cuts} && Clique $+$ & Clique && Rank/W & Clique \\\\ \\cline{6-8}\n\
%Encabezado fila 3\n\
&&&&& W-Rank & Clique & \\cite{Pardalos} && Rank/W cuts & cuts only\\\\ \\hline \\hline\n\
\%Datos\n";

//	outStream.write(encabezado, strlen(encabezado));

	for (map<Instance, vector<Corrida>, bool(*)(Instance, Instance)>::iterator corrida = corridas.begin(); corrida != corridas.end(); ++corrida)
	{
		string ubclique = to_string(corrida->second[0].UB);
		string timeclique = to_string(corrida->second[0].time);

		int bestK;
		double bestUB = corrida->second[0].UB;
		for (int K = 2; K < corrida->second.size(); ++K)
			if (corrida->second[K].UB < bestUB)
			{
				bestK = K;
				bestUB = corrida->second[K].UB;
			}

		double pardalosUB = 0.0;
		map<string,double>::iterator ut = pardalos.find(corrida->first.nombre);
		if (ut != pardalos.end())
			pardalosUB = ut->second;

		double boldUB = bestUB;
		if (corrida->second[0].UB < boldUB)
			boldUB = corrida->second[0].UB;
		if (corrida->second[1].UB < boldUB)
			boldUB = corrida->second[1].UB;
		if (pardalosUB > 0 && pardalosUB < boldUB)
			boldUB = pardalosUB;

		int p = -3;
		string filaGrafoK1("{\\tt "+corrida->first.nombre+"} & "+
				to_string(corrida->first.nvert)+"/"+
				to_string(corrida->first.dens).substr(0, 4)+" & "+
				to_string(corrida->first.alpha)+" & 1 & "+
				toString(corrida->second[1].LB,1)+ " & "+
				toString(corrida->second[1].UB,2,corrida->second[1].UB==boldUB)+ " & "+
				toString(corrida->second[0].UB,2,corrida->second[0].UB==boldUB)+ " & "+
				toString(pardalosUB,2,pardalosUB==boldUB)+" && "+
				to_string(corrida->second[1].time).substr(0, to_string(corrida->second[1].time).find_first_of('.')+3)+" & "+
				to_string(corrida->second[0].time).substr(0, to_string(corrida->second[0].time).find_first_of('.')+3)+" && "+
				toString(corrida->second[1].nrank,1)+"/"+
				toString(corrida->second[1].nwrank,1)+" & "+
				toString(corrida->second[1].nclique+corrida->second[1].ninitmodel,1)+" \\\\ ");

		while ((p = filaGrafoK1.find_first_of('_',p+3)) != string::npos)
			filaGrafoK1.insert(p, 1, '\\');

		filaGrafoK1.append("\n");
		outStream.write(filaGrafoK1.data(), filaGrafoK1.length());

		string filaGrafo("\\rowcolor[gray]{.9} & & & "+
				to_string(bestK)+" & "+
				toString(corrida->second[bestK].LB,1)+ " & "+
				toString(bestUB,2,bestUB==boldUB)+ " & & & & "+
				to_string(corrida->second[bestK].time).substr(0, to_string(corrida->second[bestK].time).find_first_of('.')+3)+" & & & "+
				toString(corrida->second[bestK].nrank,1)+"/"+
				toString(corrida->second[bestK].nwrank,1)+" & "+
				toString(corrida->second[bestK].nclique+corrida->second[bestK].ninitmodel,1)+" \\\\ ");

		while ((p = filaGrafo.find_first_of('_',p+3)) != string::npos)
			filaGrafo.insert(p, 1, '\\');

		filaGrafo.append("\n");
		outStream.write(filaGrafo.data(), filaGrafo.length());
	}

	char acotacion[] = "\\hline\n\
\\end{tabular}";

//	outStream.write(acotacion, strlen(acotacion));

	outStream.close();
}

int main(int argc, char *argv[]) {

	if ( argc < 3 )
	{
		cout << "Llamada: cosecha <directorio datos> <archivo salida>\n";
		exit(0);
	}

	string dirname(argv[1]);
	cout << dirname << endl;
//	system ("ls");

	bool(*fn_pt)(Instance, Instance) = fncomp;
	map<Instance, vector<Corrida>, bool(*)(Instance, Instance)> corridasDimacs(fn_pt);
	map<Instance, vector<Corrida>, bool(*)(Instance, Instance)> corridasRand(fn_pt);

	DIR * datosDir = opendir(dirname.data());
	struct dirent * dirEntry = readdir(datosDir);
	while (dirEntry != NULL)
	{
		if (dirEntry->d_type == _DIR_)
		{
			if (strcmp(dirEntry->d_name, "dimacs") == 0)
				leerCorridasDimacs(dirname, corridasDimacs);
			else if (strcmp(dirEntry->d_name, "rand") == 0)
				leerCorridasRand(dirname, corridasRand);
		}
		dirEntry = readdir(datosDir);
	}

	escribirTabla(corridasDimacs, string(argv[2])+"-dimacs.tex");
	corridasDimacs.clear();

	for (map<Instance, vector<Corrida>, bool(*)(Instance, Instance)>::iterator corrida = corridasRand.begin(); corrida != corridasRand.end(); ++corrida)
	{
//		cout << corrida->first.nombre << endl;
		for (int i = 0; i < corrida->second.size(); ++i)
		{
//			cout << "K=" << i << endl;
			corrida->second[i].promedio();
		}
	}

	escribirTabla(corridasRand, string(argv[2])+"-rand.tex");
	corridasRand.clear();
}
